Wavestation Developer Frequently Asked Questions

This FAQ file includes a number of comments and explanations to
supplement the developer documentation for the Wavestation series in
general (including the original keyboard, EX, and A/D), and the
Wavestation SR in particular.

The sections of this document are:
Initializing the Wavestation keyboard, EX, and A/D
Initializing the Wavestation SR
Viewing the software version of the Wavestation
Patch_Output parameter
Calculation of the LFO fade-in slope, and associated Rate_Tab table
Calculation of the Mix Envelope Parameters, including Mix_Count1 and 
     Mix_YSlope4
Computing Checksums for the Wavestation Family
Order of System Exclusive messages sent after the ALL Dump
Code for Nibblization of Bytes
Wave Sequence Notes
Sending Individual Performances via System Exclusive
Sending Individual Patches via System Exclusive
Sending Individual Items to the Wavestation SR
Vector Mix Equations

*   *   *   *   *   *   *   *   *   *   *   *

Initializing the Wavestation keyboard, EX, and A/D

1. 	Turn the power on.

2. 	Wait for the Korg logo to come up.

3. 	While the logo is still flashing, press the down cursor and the "4"
	button simultaneously. This will take you to the SPECIAL SCREEN, most of
	whose options are for development and debugging purposes and will
	destroy user data.

WARNING: This next step will initialize RAM and then copy the ROM
Performances, Patches, and Wave Sequences into banks RAM1 and RAM2. All
user Performances, Patches, and Wave Sequences will be lost.

4. 	Press the INIT RAM button to initialize RAM. The ROM Performances,
	Patches, and Wave Sequences will be copied into banks RAM1 and RAM2.

The CONT softkey will simply take you to the PERFORMANCE page, as would
happen normally on startup.

Press any of the other buttons at your own peril :-).

*   *   *   *   *   *   *   *   *   *   *   *

Initializing the Wavestation SR

This shortcut can be done at any time - not just during startup.

1. 	Simultaneously press the down cursor, BANK, EDIT, and +1/YES buttons.
	This will take you to the SPECIAL screen, which (as noted above) has
	many functions which were intended for development purposes only, and
	will destroy user data.

2. 	Cursor twice to the right; this takes you to the Init All RAM
	command.

3. 	Press the +1/YES button.

WARNING: This next step will initialize RAM and then copy the ROM
Performances, Patches, and Wave Sequences into banks RAM1 and RAM2. All
user Performances, Patches, and Wave Sequences will be lost.

4. 	An "Are You Sure" screen will appear; press the +1/YES button again
	to initialize RAM.

*   *   *   *   *   *   *   *   *   *   *   *

Viewing the software version of the Wavestation

The software version is displayed on the SPECIAL screen; see
"initializing the Wavestation keyboard, EX, and A/D" and 
"initializing the Wavestation SR" above for directions on 
how to display this screen.

*   *   *   *   *   *   *   *   *   *   *   *

Patch_Output parameter

The Patch_Output byte is bit encoded (see the figure below). This parameter 
determines the FX Bus outputs for each individual Wave, as shown on the Patch 
Bus Assignment page. This allows each Wave to be assigned to any combination 
of the four FX Buses (and, depending on the effects and effects routing, to 
the outputs themselves). Note that this parameter is only used if the Part FX 
Bus is set to "Patch;" otherwise, the Part's FX Bus value is used instead.

                      Unused Bits | Wave FX Bus Outputs (0=Off, 1=On)
                                  | D   C   B   A
Patch_Output bits:  7   6   5   4   3   2   1   0


*   *   *   *   *   *   *   *   *   *   *   *

Calculation of the LFO fade-in slope

/*
 *****************************************************************************
 * Function:
 *   update_lfox_fadein
 *
 * Parameters:
 *   indiv   *p   pointer to individual wave structure being edited
 *
 * Returns:
 *   nothing
 *
 * Global variables referenced:
 *   none
 *
 * Description:
 *   Calculate LFO fadein step size.
 *   Called when Lfo1_Fade is edited.
 *
 *****************************************************************************
 */
void    update_lfo1_fadein(indiv *p)
{
   if (Lfo1_Fade == 0)
      Lfo1_Inc = (0x7FFFFF*p->Lfo1_Amt)/127;
   else
      Lfo1_Inc = (0x7FFFFF*p->Lfo1_Amt)/(Rate_Tab[p->Lfo1_Fade]*127);
   return;
}

void    update_lfo2_fadein(indiv *p)
{
   if (Lfo2_Fade == 0)
      Lfo2_Inc = (0x7FFFFF*p->Lfo2_Amt)/127;
   else
      Lfo2_Inc = (0x7FFFFF*p->Lfo2_Amt)/(Rate_Tab[p->Lfo2_Fade]*127);
   return;
}

*   *   *   *   *   *   *   *   *   *   *   *

Rate_Tab table

Below are the values for the Rate_Tab table, as referred to above
for the computation of the LFO fade-in slope.

Rate_Tab 
.data.w     1
.data.w     2
.data.w     3
.data.w     4
.data.w     5
.data.w     6
.data.w     7
.data.w     8
.data.w     9
.data.w     10
.data.w     11
.data.w     12
.data.w     13
.data.w     14
.data.w     15
.data.w     16
.data.w     17
.data.w     18
.data.w     19
.data.w     20
.data.w     21
.data.w     22
.data.w     23
.data.w     24
.data.w     25
.data.w     26
.data.w     27
.data.w     28
.data.w     29
.data.w     30
.data.w     31
.data.w     32
.data.w     33
.data.w     34
.data.w     35
.data.w     36
.data.w     37
.data.w     38
.data.w     39
.data.w     40
.data.w     41
.data.w     42
.data.w     43
.data.w     44
.data.w     45
.data.w     46
.data.w     47
.data.w     48
.data.w     49
.data.w     50
.data.w     51
.data.w     52
.data.w     53
.data.w     54
.data.w     55
.data.w     56
.data.w     57
.data.w     58
.data.w     59
.data.w     60
.data.w     61
.data.w     62
.data.w     63
.data.w     64
.data.w     65
.data.w     66
.data.w     67
.data.w     68
.data.w     69
.data.w     70
.data.w     71
.data.w     72
.data.w     73
.data.w     74
.data.w     75
.data.w     76
.data.w     77
.data.w     78
.data.w     79
.data.w     80
.data.w     81
.data.w     83
.data.w     85
.data.w     90
.data.w     95
.data.w     100
.data.w     110
.data.w     120
.data.w     130
.data.w     140
.data.w     150
.data.w     180
.data.w     210
.data.w     240
.data.w     270
.data.w     300
.data.w     400
.data.w     500
.data.w     600
.data.w     700

*   *   *   *   *   *   *   *   *   *   *   *

Calculation of the Mix Envelope Parameters, including Mix_Count and Mix_YSlope

/*
 *****************************************************************************
 * Function:
 *   update_mix_env
 *
 * Parameters:
 *   none
 *
 * Returns:
 *   nothing
 *
 * Global variables referenced:
 *   none
 *
 * Description:
 *   Calculate mix envelope slopes after rate or level change 
 *****************************************************************************
 */
/*   Called when Rate1 is edited   */

void    update_mix_rate1(void)
{

   Mix_Count1 = Rate_Tab[Mix_Rate1];
   Mix_Count1B = Rate_Tab[Mix_Rate1];
   if (Mix_X1 >= Mix_X0)
      Mix_XSlope1 = 0x1000000*((ulong)Mix_X1-(ulong)Mix_X0)/Rate_Tab[Mix_Rate1];
   else
      Mix_XSlope1, -(0x1000000*((ulong)Mix_X0-(ulong)Mix_X1)/Rate_Tab[Mix_Rate1]);
   if (Mix_Y1 >= Mix_Y0)
      Mix_YSlope1 = 0x1000000*((ulong)Mix_Y1-(ulong)Mix_Y0)/Rate_Tab[Mix_Rate1];
   else
      Mix_YSlope1, -(0x1000000*((ulong)Mix_Y0-(ulong)Mix_Y1)/Rate_Tab[Mix_Rate1]);
   return;
}
void    update_mix_rate2(void)
{

   Mix_Count2 = Rate_Tab[Mix_Rate2];
   Mix_Count2B = Rate_Tab[Mix_Rate2];
   if (Mix_X2 >= Mix_X1)
      Mix_XSlope2 = 0x1000000*((ulong)Mix_X2-(ulong)Mix_X1)/Rate_Tab[Mix_Rate2];
   else
      Mix_XSlope2, -(0x1000000*((ulong)Mix_X1-(ulong)Mix_X2)/Rate_Tab[Mix_Rate2]);
   if (Mix_Y2 >= Mix_Y0)
      Mix_YSlope2 = 0x1000000*((ulong)Mix_Y2-(ulong)Mix_Y1)/Rate_Tab[Mix_Rate2];
   else
      Mix_YSlope2, -(0x1000000*((ulong)Mix_Y1-(ulong)Mix_Y2)/Rate_Tab[Mix_Rate2]);
   return;
}
void    update_mix_rate3(void)
{

   Mix_Count3 = Rate_Tab[Mix_Rate3];
   Mix_Count3B = Rate_Tab[Mix_Rate3];
   if (Mix_X3 >= Mix_X2)
      Mix_XSlope3 = 0x1000000*((ulong)Mix_X3-(ulong)Mix_X2)/Rate_Tab[Mix_Rate3];
   else
      Mix_XSlope3, -(0x1000000*((ulong)Mix_X2-(ulong)Mix_X3)/Rate_Tab[Mix_Rate3]);
   if (Mix_Y1 >= Mix_Y0)
      Mix_YSlope3 = 0x1000000*((ulong)Mix_Y3-(ulong)Mix_Y2)/Rate_Tab[Mix_Rate3];
   else
      Mix_YSlope3, -(0x1000000*((ulong)Mix_Y2-(ulong)Mix_Y3)/Rate_Tab[Mix_Rate3]);
   return;
}
void    update_mix_rate4(void)
{

   Mix_Count4 = Rate_Tab[Mix_Rate4];
   Mix_Count4B = Rate_Tab[Mix_Rate4];
   if (Mix_X4 >= Mix_X3)
      Mix_XSlope4 = 0x1000000*((ulong)Mix_X4-(ulong)Mix_X3)/Rate_Tab[Mix_Rate4];
   else
      Mix_XSlope4, -(0x1000000*((ulong)Mix_X3-(ulong)Mix_X4)/Rate_Tab[Mix_Rate4]);
   if (Mix_Y1 >= Mix_Y0)
      Mix_YSlope4 = 0x1000000*((ulong)Mix_Y4-(ulong)Mix_Y3)/Rate_Tab[Mix_Rate4];
   else
      Mix_YSlope4, -(0x1000000*((ulong)Mix_Y3-(ulong)Mix_Y4)/Rate_Tab[Mix_Rate4]);
   return;
}


/*   Called when point 0 mix is edited.   */

void    update_mix_level0(void)
{
   update_mix_rate1();   
   return;
}

/*   Called when point 1 mix is edited.   */

void    update_mix_level1(void)
{
   update_mix_rate1();   
   update_mix_rate2();   
   return;
}


/*   Called when point 2 mix is edited.   */

void    update_mix_level2(void)
{
   update_mix_rate2();   
   update_mix_rate3();   
   return;
}


/*   Called when point 3 mix is edited.   */

void    update_mix_level3(void)
{
   update_mix_rate3();   
   update_mix_rate4();   
   return;
}


/*   Called when point 4 mix is edited.   */

void    update_mix_level4(void)
{
   update_mix_rate4();   
   return;
}

*   *   *   *   *   *   *   *   *   *   *   *

Computing Checksums for the Wavestation Family

The header bytes are excluded from the computation of the checksum. These include:

   11110000 (F0)   System Exclusive status byte
   01000010 (42)   Korg ID
   0011nnnn (3n)   Format ID, n = channel number
   00101000 (28)   Wavestation device ID
   0mmmmmmm        Message type

The checksum is computed by adding up all the bytes of the message
(using the nibble-ized data), excluding the header, and then performing 
a mod 128 on the sum.

The message below is from Stefano VOULAZ (voulaz@pn.itnet.it).

= = = = = = = = = = = = 

Thus, I had to go in DEPTH with sysex! The correct way to compute the checksum
for WS messages is described in the WS FAQ (somewhere on the net) but it is not
as precise as I needed (and as someone may need). Yes, all 'Nibble Data' are
summed together one by one and the sum is ANDed with 127. But the few bytes
to be excluded are all those of sysex header, INCLUDING any parameter listed
in the sysex message description, such as "Performance Number", "Bank
Number" and so on.
That is, ONLY NIBLE DATA are checksummed.

Here follows the algorythm I wrote in C++. lpbyData points to the first
nibble data and dwLength is... the counter of nibble data (Gee!). Note that
by using a reference (BYTE&) I both check the incoming checksum and set it
to the (eventually) correct one. Thus I use the same call for
setting/checking the checksum.

BOOL CWSData::Checksum(BYTE far* lpbyData, DWORD dwLength, BYTE& byCheck)
{
	BYTE byChecksum = 0;
	while (dwLength--) byChecksum += *(lpbyData + dwLength);
	byChecksum &= 0x7F;	
	if (byChecksum == byCheck){
		return TRUE;
	}
	else{
		byCheck = byChecksum;
		return FALSE;
	}
}

Name....: Stefano VOULAZ
E-mail..: voulaz@pn.itnet.it
Mail....: Viale Gran San Bernardo, 25A
          11100 AOSTA - ITALY
Phone...: +39-10-6002987 (work time)
          +39-10-6511954 (free time)
          +39-165-40334  (weekend)
                                  
*   *   *   *   *   *   *   *   *   *   *   *

Order of System Exclusive messages sent after the ALL Dump

Order of System Exclusive messages sent after the ALL Dump for the 
Wavestation SR (the A/D is similar, but without the dumps labeled SR):

Performance Map Dump Expanded
Performance Map Dump SR
Multi Mode Setup Dump Expanded
Multi Mode Setup Dump SR
All Performances (RAM3)
All Patches (RAM3)
All Wave Sequences (RAM3)
System Setup Dump Expanded
System Setup Dump SR

The manual seems to suggest a different order - sorry if that caused
any confusion.

*   *   *   *   *   *   *   *   *   *   *   *

Code for Nibblization of Bytes

rcv_nybbles:
   mov.l   @sp+,@rcv_nybbles_pc:16          ; Address we called from
   sub.l   #1,loop_cnt1                     ; SCB goes until -1 so compensate
nybbles_loop:
   ;repeat
      wait_for_interrupt                    ; Wait for byte at MIDI IN port
      bset.b   #MIDI_LED,@led_status:16     ; Turn on MIDI LED....
      mov.b   @led_status,@LED_OUTPUT_PORT  ; Write it out to light LED
      set_device_timer      midi_in,#2000   ; Keep it lit for 2 seconds.
      mov.b   current_byte,@nybble_data:16  ; Save received data
      add.b   current_byte,@check_sum:16    ; Add received data to checksum
      wait_for_interrupt                    ; Wait for next MIDI IN byte 
      add.b   current_byte,@check_sum:16    ; Add received data to checksum
      shll.b   #4,current_byte              ; Shift data left 4 bits
      or.b   @nybble_data:16,current_byte   ; Or 2 halves of data together
      mov.b   current_byte,@destin_ptr+     ; Save in dest and increment
      scb/f   loop_cnt1,nybbles_loop        ; Subtract from count and branch
   ;until eq -1;                            ; until count is -1
   wait_for_interrupt                       ; Get another byte
   add.b   current_byte,@check_sum:16       ; Add it to checksum
   wait_for_interrupt                       ; Get another byte
   add.b   current_byte,@check_sum:16       ; Add it to checksum
   mov.l   @rcv_nybbles_pc:16,r0            ; Get return address
   jmp   @r0                                ; Return

*   *   *   *   *   *   *   *   *   *   *   *

Wave Sequence Notes

In the early design stages of the Wavestation, the designers were concerned 
that there be no arbitrary limits on the number of steps in a Wave Sequence. 
To allow for maximum flexibility in this regard, individual Wave Sequences are 
not stored discreetly. The waveseq data contains the basic information about 
the wave sequence, including the starting step, the loop, modulation settings, 
and so on, but the step data (including PCM wave, tuning, duration, crossfade, 
etc.) for all 32 Wave Sequences in a Bank are stored together in a single, 
massive block of data, the wavestep_block, and indexed by their number 
within that block. The waveseq data stores the number of the starting step, 
and the individual wavestep entries store links to both the step before and 
the step after their position in the sequence.

Most of the entries in the Wave Sequence Data Structure (as described in the 
Wavestation System Exclusive implementation, included in the Reference Guide 
for all Wavestation models) are reasonably self-explanitory, but several 
subjects require more comments:

1. The first wavstep entry is special; it is used as the stop step,
   entered at the end of every Wave Sequence.

2. Differences between WS_Link,WS_Slink and WS_Start_Step.

   The WS_Link is an index to the very first step in a 
   Wave Sequence. All wavesteps after this one are linked
   with the WS_Flink and WS_Blink.

   The WS_Slink is the index of the start wavestep in the
   Wave Sequence (which does not have to be the first step).
   
   The WS_Start_Step is the step number of the start wavestep
   relative to the start of the Wave Sequence, whereas the WS_Slink
   is an index relative to the start of wavestep memory. 
   
3. WS_Flink and WS_Blink

   These are the links that link all the steps in a Wave Sequence.
   WS_Flink is the wavestep index of the wavestep following
   this one. If it is 0 then this is last step. WS_Blink is 
   the wavestep index of the wavestep preceeding this one.
   An empty step has WS_Flink and WS_Blink set to 0xffff.

4. WS_Llink

   This is set to 0xffff except for the last step of a Wave Sequence.
   If it is set to 0xfffe it means that looping is off and the
   Wave Sequence should terminate. Otherwise if WS_Llink is set to any
   other value it is the wavestep index of the start of the loop.
   
5. WS_Mod_Index

   This is used for Dynamic Modulation of the Wave Sequence; it is a 
   pre-calculated using the waveseq blocks WS_Mod_Amt parameter (as 
   ahown below), allowing faster realtime response. Basically it
   breaks the Wave Sequence down into a +/- 64 range of modulation values
   with the Wave Sequence start step located at 0. The WS_Mod_Index value
   is the modulation value which causes this wavestep to sound.
   The WS_Mod_Index of the wavstep following this one is the modulation
   value that causes this step to stop sounding or to start xfading
   away. Below is a code fragment used in calculating this value.

   
void     update_ws_mod(void)
{
    register long   seqstep;        /* Step array element number */
    register long   steptotal = 1;
    register long   stepcount = 0;
    ubyte   startstep;
    byte    modamount;
    word    temp;
    wavesequence    *waveseq_ptr = Cur_Waveseq_Ptr();
    wavestep        *wavestep_mem_ptr;
    
    
    if ((seqstep = &waveseq_ptr->WS_Link) == 0)
        return;
    startstep = waveseq_ptr->WS_Start_Step;
    modamount = waveseq_ptr->WS_Mod_Amt;
    if (modamount < 0)
        modamount = -modamount;

/* Calculate number of steps in wave sequence */
    while ((seqstep = &Cur_Wavestep_Mem_Ptr(seqstep)->WS_Flink) !=0)
    {
        steptotal +=1;  
    }
    
/* Are we doing static or dynamic style of modulation */
    if ((127 & waveseq_ptr->WS_Mod_Src) <= 3)
    {
        seqstep = &waveseq_ptr->WS_Link;
        while (seqstep != 0)
        {
            wavestep_mem_ptr = Cur_Wavestep_Mem_Ptr(seqstep);

            if (modamount == 0)
                wavestep_mem_ptr->WS_Mod_Index = 127;
            else
            {
                if (stepcount >= startstep)
                {
                    temp =((8191/modamount) *(stepcount - startstep)
                         /(steptotal-startstep));
                    if (temp > 127)
                        temp = 127;
                    wavestep_mem_ptr->WS_Mod_Index = temp;
                }
                else
                {
                    temp =  (8191/modamount)*(startstep-stepcount)/(startstep);
                    if (temp > 127)
                        temp = 127;
                    wavestep_mem_ptr->WS_Mod_Index = temp;
                }
            }
            stepcount++;
            seqstep = &wavestep_mem_ptr->WS_Flink;
        }
    }
}
	

*   *   *   *   *   *   *   *   *   *   *   *


Sending Individual Performances via System Exclusive

When sending a Performance and its Patches, its best to send the
Performance itself last. When a Patch is received via MIDI SysEx, the
current Part is always changed to reference that new Patch, to make
auditioning individual Patches easy; this means, however, that the
Performance is altered. To ensure that the Performance remains the same,
send it after you send its Patches.

*   *   *   *   *   *   *   *   *   *   *   *

Sending Individual Patches via System Exclusive

Incoming data for single Patches (as opposed to an entire bank of
Patches) is written first to the edit buffer, and then - on the WRITE
command - is written into program RAM. If the Wavestation receives new
data into the buffer before finishing the write to program RAM, this
would obviously cause problems.

To be absolutely sure that it is OK to send the next Patch, it would be
best to wait for a Write Complete (or Write Error) message. We
understand, however, that one cannot necessarily count on the
Wavestation MIDI Out being connected to the computer, and so a timeout
is probably necessary.

The actual amount of time that the Wavestation takes to execute a Write
varies very slightly depending on which page it is on, since the screen
may need to be refreshed. Two seconds is sufficient, for instance, for
the Performance Select page (for the keyboard and A/D); on the Edit
Performance page, however, the operation takes a little bit longer. We
advise that you make your timeout longer than two seconds.

If any of the patches contain Wave Sequences, you will also have to send
the entire Wave Sequence Data, which takes time as well. We realize that
these delays may make auditioning single Performances somewhat
cumbersome, but unfortunately this cannot be changed.

*   *   *   *   *   *   *   *   *   *   *   *

Sending Individual Items to the Wavestation SR

Due to problems with the implementation of "Are You Sure" commands in the
Wavestation SR interface, the Patch and Performance Write commands do
not work properly from SysEx; they wait for a "Yes" confirmation which
SysEx cannot provide. Unfortunately, this problem was not discovered
until almost two years after its release, at which point the project was
long over and the SR's primary programmer was working elsewhere.
Therefore, no bug fixes are available; we realize that this is very
unfortunate, and wish that the situation was otherwise.

Note that the difficulties below are only issues with the Wavestation SR,
and not with the original keyboard, EX, or A/D.

Due to the problem with Patch Write, it is not possible to save
individual Patches to locations in the Wavestation SR memory. Banks of
Patches may be saved as usual, however.

Saving of individual Performances and Multisets is possible, but only to
the location of the currently selected Performance or Multiset, as
described below.

The Execute Write command by itself (without setting any of the Write
params) seems to work for both Patches and Performances - as long as the
object showing on the screen is the object that you want to save.

You should be able to save both individual Performances and Multisets by
doing the following:

1.	Edit as desired.

2. 	For Performances: using Param 4, set the MIDI mode to Multi and then
	to Perf. For Multisets, select Perf and then Multi. Unfortunately, this
	toggling seems necessary to get to the top page.

3. 	Send the Execute Write command. The object is stored in its current
	location.

The above method offers no way to store Patches, as youve probably noticed.
Also, the object is stored in the current location in the Bank.

*   *   *   *   *   *   *   *   *   *   *   *

Vector Mix Equations

{
    switch (sys.mix_env_point)
    {
     case 0:
         xp = patch_ptr->Mix_X0 + patch_ptr->Mix_Y0 - 255;
         yp = patch_ptr->Mix_Y0 - patch_ptr->Mix_X0;
         break;
     case 1:
         xp = patch_ptr->Mix_X1 + patch_ptr->Mix_Y1 - 255;
         yp = patch_ptr->Mix_Y1 - patch_ptr->Mix_X1;
         break;
     case 2:
         xp = patch_ptr->Mix_X2 + patch_ptr->Mix_Y2 - 255;
        yp = patch_ptr->Mix_Y2 - patch_ptr->Mix_X2;
         break;
     case 3:
         xp = patch_ptr->Mix_X3 + patch_ptr->Mix_Y3 - 255;
         yp = patch_ptr->Mix_Y3 - patch_ptr->Mix_X3;
         break;
     case 4:
         xp = patch_ptr->Mix_X4 + patch_ptr->Mix_Y4 - 255;
         yp = patch_ptr->Mix_Y4 - patch_ptr->Mix_X4;
         break;
    }
   
The code below limits the joystick data to a diamond-shaped range:

                    C
            _________________  
           | clip   *   clip |
           |      *   *      |
           |    *       *    |
           |  *           *  |
         A |*               *| B
           |  *           *  |
           |    *       *    |
           | clip *   * clip |
           |________*________| 

                    D


   /* Now calculate the A,B,C, and D % from the x,y coordinates */
   
    if (xp > 127)   /* Limit range to -128 - 127 */
        xp = 127;
    else if (xp < -128)
        xp = -128;
 
    if (yp > 127)   /* Limit range to -128 - 127 */
        yp = 127;
    else if (yp < -128)
        yp = -128;
        
    xp += 128;
    yp += 128;
 
    d = xp*yp/645;   /* Calculate individual wave % */
    c = xp*(255 - yp)/645; /* 645=(255^2/100)*127/128 */
    b = (255-xp)*(255-yp)/645;
    a = 100 - b - c - d;
}
Vector Mix Equations

*   *   *   *   *   *   *   *   *   *   *   * 
